﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;

namespace NetfractionHub.Network
{
	public static class IPHelper
	{
		private const int IPv4_ADDRESS_BYTES = 4;
		private const int IPv6_ADDRESS_BYTES = 16;

		public static bool IsIPInRange(IPAddress ipToCheck, IPAddress rangeStart, IPAddress rangeEnd)
		{
			if (ipToCheck.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
			{
				if (rangeStart.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
					return IsIPv4InRange(ipToCheck, rangeStart, rangeEnd);
				else
					return false;
			}
			else if (ipToCheck.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
			{
				if (rangeStart.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
					return IsIPv6InRange(ipToCheck, rangeStart, rangeEnd);
				else
					return false;
			}
			return false;
		}

		public static bool ParseIPEndPoint(string input, out IPEndPoint ipep)
		{
			input = input.Trim();
			ipep = null;

			System.Net.IPAddress ipAddr = null;
			int port;
			string[] parts = null;

			// if true, treat as an IPv6 address
			// in IPv6 the format should be [::1]:4501
			if (input[0] == '[')
				parts = input.Substring(1).Split(new string[] { "]:" }, 2, StringSplitOptions.RemoveEmptyEntries);
			else
				parts = input.Split(new char[] { ':' }, 2);

			// check that both an IP and port number were retrieved
			if (parts.Length != 2)
				return false;

			bool parseSuccess = System.Net.IPAddress.TryParse(parts[0], out ipAddr);
			if (!parseSuccess)
				return false;

			parseSuccess = int.TryParse(parts[1], out port);
			if (!parseSuccess && port >= System.Net.IPEndPoint.MinPort && port <= System.Net.IPEndPoint.MaxPort)
				return false;

			ipep = new System.Net.IPEndPoint(ipAddr, port);

			return true;
		}

		public static bool ParseDnsEndPoint(string input, out DnsEndPoint dep)
		{
			dep = null;

			string host;
			int port;
			string[] parts = null;
			parts = input.Split(new char[] { ':' }, 2);

			// check that both an IP and port number were retrieved
			if (parts.Length != 2)
				return false;

			IPAddress[] ips = Dns.GetHostAddresses(parts[0]);
			bool parseSuccess = ips.Length > 0;
			if (parseSuccess)
				host = parts[0];
			else
				return false;

			parseSuccess = int.TryParse(parts[1], out port);
			if (!parseSuccess && port >= System.Net.IPEndPoint.MinPort && port <= System.Net.IPEndPoint.MaxPort)
				return false;
			
			dep = new System.Net.DnsEndPoint(host, port);

			return true;
		}

		private static bool IsIPv4InRange(IPAddress ipToCheck, IPAddress rangeStart, IPAddress rangeEnd)
		{
			return IsIPInRange(ipToCheck, rangeStart, rangeEnd, IPv4_ADDRESS_BYTES);
		}

		private static bool IsIPv6InRange(IPAddress ipToCheck, IPAddress rangeStart, IPAddress rangeEnd)
		{
			return IsIPInRange(ipToCheck, rangeStart, rangeEnd, IPv6_ADDRESS_BYTES);
		}

		private static bool IsIPInRange(IPAddress ipToCheck, IPAddress rangeStart, IPAddress rangeEnd, int ipBytesLength)
		{
			byte[] rIp = ipToCheck.GetAddressBytes();
			byte[] rStart = rangeStart.GetAddressBytes();
			byte[] rEnd = rangeEnd.GetAddressBytes();

			bool inRange = false;
			for (int i = 0; i < ipBytesLength; i++)
			{
				if (rIp[i] >= rStart[i] && rIp[i] <= rEnd[i])
				{
					inRange = true;
				}
				else
				{
					inRange = false;
					break;
				}
			}
			return inRange;
		}
	}
}
